//向量类
var Vector2D = class {
	//构造函数
    constructor(x=0,y=0) {
		this._x=x;
		this._y=y;
    }
	/**绘制一条线段*/
	draw(graphics, color='#000'){
		graphics.strokeStyle(0, color);
		graphics.moveTo(0, 0);
		graphics.lineTo(this._x, this._y);
		graphics.stroke();
	}
	/**复制向量*/
	clone(){
		return new Vector2D(this._x, this._y);
	}
	/**将当前向量变成0向量*/
	zero(){
		this._x=0;
		this._y=0;
		return this;
	}
	/**判断是否是0向量*/
	isZero(){
		return this._x === 0 && this._y === 0;
	}
	/**设置向量的大小*/
	set length(value){
		var a=this.angle;
		this._x=Math.cos(a) * value;
		this._y=Math.sin(a) * value;
	}
	get length(){
		return Math.sqrt(this.lengthSQ);
	}
	/**获取当前向量大小的平方*/
	get lengthSQ(){
		return this._x * this._x + this._y * this._y;
	}
	/**设置向量的角度*/
	set angle(value){
		var len=this.length;
		this._x=Math.cos(value) * len;
		this._y=Math.sin(value) * len;
	}
	/**得到的是弧度 */
	get angle(){
		return Math.atan2(this._y, this._x);
	}
	/**得到的是角度 */
	get angle2(){
		return this.angle*180/Math.PI;
	}
	/**将当前向量转化成单位向量(即设置向量的模为1)*/
	normalize(){
		if (this.length === 0){
			this._x=1;
			return this;
		}
		var len=this.length;
		this._x/=len;
		this._y/=len;
		return this;
	}
	/**截取当前向量*/
	truncate(max){
		this.length=Math.min(max, this.length);
		return this;
	}
	/**反转向量*/
	reverse(){
		this._x*=-1;
		this._y*=-1;
		return this;
	}
	/**判断当前向量是否是单位向量*/
	isNormalized(){
		return this.length == 1.0;
	}
	/**向量积*/
	dotProd(v2){
		return this._x * v2.x + this._y * v2.y;
	}
	/**判断两向量是否交叉,为0时不交叉*/
	crossProd(v2){
		return this._x * v2.y - this._y * v2.x;
	}
	/**返回两向量夹角的弦度值*/
	static angleBetween(v1, v2){
		if (!v1.isNormalized())
			v1=v1.clone().normalize();
		if (!v2.isNormalized())
			v2=v2.clone().normalize();
	
		return Math.acos(v1.dotProd(v2));
	}
	/**判定给定的向量是否在本向量的左侧或右侧，左侧返回-1，右侧返回1*/
	sign(v2){
		return this.perp.dotProd(v2)< 0? -1:1;
	}
	/**返回与本向量垂直的向量(即自身顺时针旋转90度，得到一个新向量)*/
	get perp(){
		return new Vector2D(-this._y, this._x);
	}
	/**返回当前向量与V2的距离*/
	dist(v2){
		return Math.sqrt(this.distSQ(v2));
	}
	/**返回当前向量与V2的距离的平方*/
	distSQ(v2){
		var dx=v2.x - this._x;
		var dy=v2.y - this._y;
		return dx * dx + dy * dy;
	}
	/**两向量相加*/
	add(v2){
		return new Vector2D(this._x + v2.x, this._y + v2.y);
	}
	/**两向量相减*/
	subtract(v2){
		return new Vector2D(this._x - v2.x, this._y - v2.y);
	}
	/**数与向量的乘积*/
	multiply(value){
		return new Vector2D(this._x * value, this._y * value);
	}
	/**数与向量的商*/
	divide(value){
		return new Vector2D(this._x / value, this._y / value);
	}
	/**判断两向量是否相等*/
	equals(v2){
		return this._x == v2.x && this._y == v2.y;
	}
	/**设置X坐标*/
	set x(value){
		this._x=value;
	}
	get x(){
		return this._x;
	}
	/**设置Y坐标*/
	set y(value){
		this._y=value;
	}
	get y(){
		return this._y;
	}
}
//SM:https://blog.csdn.net/weixin_34308389/article/details/86158773
//交通工具类
class Vehicle {
	 //构造函数
	 constructor(display) {
		this._display=display;//显示对象
		this._position=new Vector2D();//位置
		this._velocity=new Vector2D();//速度
		this._mass=1.0;//质量
		this._maxSpeed=5;//最大速度
		this._angle=0;//角度
		this._edgeBehavior='bounce';
	}
	update(){
		//处理边界行为
		if (this._edgeBehavior=='warp') {
			this.wrap();
		} else if (this._edgeBehavior=='bounce') {
			this.bounce();
		}
		 //更新x,y坐标值
		this._display.x=this._position.x;
		this._display.y=this._position.y;
		//设置最大速度
		this._velocity.truncate(this._maxSpeed);
		//根据速度更新坐标向量
		this._position=this._position.add(this._velocity);
		//处理旋转角度
		this._angle=this._velocity.angle2;
		if(!!this._display.setAngle){
			this._display.setAngle(this._angle);
		}
		if(!!this._display.rotation){
			this._display.rotation=this._angle;
		}
	}
	
	//反弹
	bounce(){
		if (this.stage!=null) {
			if (this.position.x>this.stage.width) {
				this.position.x=this.stage.width;
				this.velocity.x*=-1;
			} else if (this.position.x<0) {
				this.position.x=0;
				this.velocity.x*=-1;
			}
			if (this.position.y>this.stage.height) {
				this.position.y=this.stage.height;
				this.velocity.y*=-1;
			} else if (this.position.y<0) {
				this.position.y=0;
				this.velocity.y*=-1;
			}
		}
	}
	//屏幕环绕
	wrap(){
		if (this.stage!=null) {
			if (this.position.x>this.stage.width) {
				this.position.x=0;
			}
			if (this.position.x<0) {
				this.position.x=this.stage.width;
			}
			if (this.position.y>this.stage.height) {
				this.position.y=0;
			}
			if (this.position.y<0) {
				this.position.y=this.stage.height;
			}
		}           
	}
	/**设置:屏幕环绕(wrap)，还是反弹{bounce}*/
	set edgeBehavior(value){
		this._edgeBehavior=value;
	}
	get edgeBehavior() {
		return this._edgeBehavior;
	}
	/**设置质量*/
	set mass(value){
		this._mass=value;
	}
	get mass() {
		return this._mass;
	}
	/**设置最大速度*/
	set maxSpeed(value){
		this._maxSpeed=value;
	}
	get maxSpeed() {
		return this._maxSpeed;
	}
	get display(){
		return this._display;
	}
	set display(value){
		this._display=value;
	}
	/**设置位置*/
	set position(value){
		this._position=value;
		this._display.x=this._position.x;
		this._display.y=this._position.y;
	}
	get position() {
		return this._position;
	}
	/**设置力*/
	set velocity(value){
		this._velocity=value;
	}
	get velocity() {
		return this._velocity;
	}
	/**设置x轴方向的位置*/
	set x(value){
		this._display.x=value;
		this._position.x=value;
	}
	/**设置y轴方向的位置*/
	set y(value){
		this._display.y=value;
		this._position.y=value;
	}
}
//操作交通工具类
class SteeredVehicle extends Vehicle{
	constructor(display) {
		super(display);
		this._maxForce= 1;//最大转向力
        this._steeringForce=new Vector2D;//转向速度
        this._arrivalThreshold= 100;
        this._wanderAngle= 0;
        this._wanderDistance= 10;
        this._wanderRadius= 5;
        this._wanderRange= 1;
        this._pathIndex = 0;
        this._pathThreshold= 20;
        this._avoidDistance= 300;//避开的距离
        this._avoidBuffer= 20;
        this._inSightDist= 200;//视线距离
        this._tooCloseDist= 60;//靠太近
	}
	set maxForce(value)
	{
		this._maxForce = value;
	}
	get maxForce()
	{
		return this._maxForce;
	}
		
	set arriveThreshold(value)
	{
		this._arrivalThreshold = value;
	}
	get arriveThreshold()
	{
		return this._arrivalThreshold;
	}
		
	set wanderDistance(value)
	{
		this._wanderDistance = value;
	}
	get wanderDistance()
	{
		return this._wanderDistance;
	}
		
	set wanderRadius(value)
	{
		this._wanderRadius = value;
	}
	get wanderRadius()
	{
		return this._wanderRadius;
	}
		
	set wanderRange(value)
	{
		this._wanderRange = value;
	}
	get wanderRange()
	{
		return this._wanderRange;
	}
		
	set pathIndex(value)
	{
		this._pathIndex = value;
	}
	get pathIndex()
	{
		return this._pathIndex;
	}
		
	set pathThreshold(value)
	{
		this._pathThreshold = value;
	}
	get pathThreshold()
	{
		return this._pathThreshold;
	}
		
	set avoidDistance(value)
	{
		this._avoidDistance = value;
	}
	get avoidDistance()
	{
		return this._avoidDistance;
	}
		
	set avoidBuffer(value)
	{
		this._avoidBuffer = value;
	}
	get avoidBuffer()
	{
		return this._avoidBuffer;
	}
		
	set inSightDist(value)
	{
		this._inSightDist = value;
	}
	get inSightDist()
	{
		return this._inSightDist;
	}
		
	set tooCloseDist(value)
	{
		this._tooCloseDist = value;
	}
	get tooCloseDist()
	{
		return this._tooCloseDist;
	}
	update()
	{
		this._steeringForce.truncate(this._maxForce);//限制为最大转向速度，以避免出现突然的大转身
		this._steeringForce = this._steeringForce.divide(this._mass);//惯性的体现
		this._velocity = this._velocity.add(this._steeringForce);
		this._steeringForce = new Vector2D();
		super.update();
	}
	//寻找行为	就是角色本身试图移动（包括转向）到目标位置(这个位置可能是固定的，也可能是移动的)。
	seek(target)
	{
		var desiredVelocity = target.subtract(this._position);
		desiredVelocity.normalize();
		desiredVelocity = desiredVelocity.multiply(this._maxSpeed);
		var force = desiredVelocity.subtract(this._velocity);
		this._steeringForce = this._steeringForce.add(force);
	}
	//避开(flee)行为	它跟寻找(seek)行为正好是相反的，可以通俗的理解为：“既然发现了目标，那么就调头逃跑吧”，所以代码上只要改一行即可
	flee(target)
	{
		var desiredVelocity = target.subtract(this._position);
		desiredVelocity.normalize();
		desiredVelocity = desiredVelocity.multiply(this._maxSpeed);
		var force = desiredVelocity.subtract(this._velocity);
		this._steeringForce = this._steeringForce.subtract(force);
	}
	//到达(arrive)行为  到达行为其实跟寻找行为很相似，区别在于：寻找行为发现目标后，不断移动靠近目标，但速度不减，所以会出现最终一直在目标附近二头来回跑，停不下来。而到达行为在靠近目标时会慢慢停下来，最终停在目标点。
	arrive(target)
	{
		var desiredVelocity = target.subtract(this._position);
		desiredVelocity.normalize();
			
		var dist = this._position.dist(target);
		if(dist > this._arrivalThreshold)
		{
			desiredVelocity = desiredVelocity.multiply(this._maxSpeed);
		}
		else
		{
			desiredVelocity = desiredVelocity.multiply(this._maxSpeed * dist / this._arrivalThreshold);
		}
			
		var force = desiredVelocity.subtract(this._velocity);
		this._steeringForce = this._steeringForce.add(force);
	}
	//追捕(pursue)行为  	追捕跟寻找很类似，不过区别在于：寻找(seek)是发现目标后，以预定的速度向目标靠拢，不管目标跑得多快还是多慢，所以如果目标比寻找者(seeker)要移动得快，seeker永远是追不上的；而追捕行为是要在目标前进的路上，提前把目标拦截到，也可以理解为先预定一个（target前进路线上的）目标位置，然后再以寻找行为接近该位置，所以只要预定目标位置计算得合理，就算追捕者的速度要慢一点儿，也是完全有可能把目标给抓住的。
	pursue(target)
	{
		var lookAheadTime = position.dist(target.position) / this._maxSpeed;
		var predictedTarget = target.position.add(target.velocity.multiply(lookAheadTime));
		seek(predictedTarget);
	}
	//躲避(evade)行为    躲避跟追捕正好相反，可以理解为：如果我有可能挡在目标前进的路线上了，我就提前回避，让开这条道。(俗话：好狗不挡道)
	evade(target)
	{
		var lookAheadTime = position.dist(target.position) / this._maxSpeed;
		var predictedTarget = target.position.subtract(target.velocity.multiply(lookAheadTime));
		flee(predictedTarget);
	}
	//漫游（wander）行为    顾名思义，就是要让物体在屏幕上漫不经心的闲逛。可能大家首先想到的是让速度每次随机改变一些（类似布朗运动），但很快您就会发现这样做的结果：物体象抽风一样在屏幕上乱动，一点都不连续，体现不出“漫不经心”闲逛的特征。所以我们需要一种更为平滑的处理算法：
	wander()
	{
		var center = this.velocity.normalize().multiply(this._wanderDistance);
		var offset = new Vector2D(0);
		offset.length = this._wanderRadius;
		offset.angle = this._wanderAngle;
		this._wanderAngle += Math.random() * this._wanderRange - this._wanderRange * .5;
		var force = center.add(offset);
		this._steeringForce = this._steeringForce.add(force);
	}
		
	avoid(circles)
	{
		for(var i = 0; i < circles.length; i++)
		{
			var circle = circles[i];
			var heading = this._velocity.clone().normalize();
				
			// vector between circle and vehicle:
			var difference = circle.position.subtract(this._position);
			var dotProd = difference.dotProd(heading);
				
			// if circle is in front of vehicle...
			if(dotProd > 0)
			{
				// vector to represent "feeler" arm
				var feeler = heading.multiply(this._avoidDistance);
				// project difference vector onto feeler
				var projection = heading.multiply(dotProd);
				// distance from circle to feeler
				var dist = projection.subtract(difference).length;
					
				// if feeler intersects circle (plus buffer),
				//and projection is less than feeler length,
				// we will collide, so need to steer
				if(dist < circle.radius + this._avoidBuffer &&
					projection.length < feeler.length)
				{
					// calculate a force +/- 90 degrees from vector to circle
					var force = heading.multiply(this._maxSpeed);
					force.angle += difference.sign(this._velocity) * Math.PI / 2;
						
					// scale this force by distance to circle.
					// the further away, the smaller the force
					force = force.multiply(1.0 - projection.length /
													feeler.length);
						
					// add to steering force
					this._steeringForce = this._steeringForce.add(force);
						
					// braking force
					this._velocity = this._velocity.multiply(projection.length / feeler.length);
				}
			}
		}
	}
		
	followPath(path, loop = false)
	{
		var wayPoint = path[this._pathIndex];
		if(wayPoint == null) return;
		if(this._position.dist(wayPoint) < this._pathThreshold)
		{
			if(this._pathIndex >= path.length - 1)
			{
				if(loop)
				{
					this._pathIndex = 0;
				}
			}
			else
			{
				this._pathIndex++;
			}
		}
		if(this._pathIndex >= path.length - 1 && !loop)
		{
			arrive(wayPoint);
		}
		else
		{
			seek(wayPoint);
		}
	}
		
	flock(vehicles)
	{
		var averageVelocity = this._velocity.clone();
		var averagePosition = new Vector2D();
		var inSightCount = 0;
		for(var i = 0; i < vehicles.length; i++)
		{
			var vehicle = vehicles[i];
			if(vehicle != this && this.inSight(vehicle))
			{
				averageVelocity = averageVelocity.add(vehicle.velocity);
				averagePosition = averagePosition.add(vehicle.position);
				if(this.tooClose(vehicle)) this.flee(vehicle.position);
				inSightCount++;
			}
		}
		if(inSightCount > 0)
		{
			averageVelocity = averageVelocity.divide(inSightCount);
			averagePosition = averagePosition.divide(inSightCount);
			this.seek(averagePosition);
			this._steeringForce.add(averageVelocity.subtract(this._velocity));
		}
	}
		
	inSight(vehicle)      
	{
		if(this._position.dist(vehicle.position) > this._inSightDist) return false;
		var heading = this._velocity.clone().normalize();
		var difference = vehicle.position.subtract(this._position);
		var dotProd = difference.dotProd(heading);
			
		if(dotProd < 0) return false;
		return true;
	}
		
	tooClose(vehicle)
	{
		return this._position.dist(vehicle.position) < this._tooCloseDist;
	}
}